/*
 * Copyright 2013 The Scripps Research Institute
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.scripps.fl.entrez.internal;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.scripps.fl.entrez.EUtilsException;

public abstract class EUtilsWebSessionBase extends WebSessionBase {

	private static final Logger log = LoggerFactory.getLogger(EUtilsWebSessionBase.class);
	protected static final boolean DEBUGGING = false;

	protected static String EINFO_URL = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/einfo.fcgi";
	protected static String ESEARCH_URL = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi";
	protected static String ESUMMARY_URL = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi";
	protected static String EPOST_URL = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/epost.fcgi";

	private String eUtilsVersion = "2.0";
	private String tool;
	private String email;
	private long delay = 333;
	private long previous;
	private long count;

	public EUtilsWebSessionBase(String tool, String userEmail) {
		setTool(tool);
		setEmail(userEmail);
	}

	public HttpClient getHttpClient() {
		return HttpClientBaseSingleton.getInstance().getHttpClient();
	}

	public void setHttpClient(HttpClient client) {
		HttpClientBaseSingleton.getInstance().setHttpClient(client);
	}

	public String getEUtilsVersion() {
		return eUtilsVersion;
	}

	public void setEUtilsVersion(String eUtilsVersion) {
		this.eUtilsVersion = eUtilsVersion;
	}

	public String getTool() {
		return this.tool;
	}

	public String getEmail() {
		return this.email;
	}

	public void setTool(String tool) {
		this.tool = tool;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	// protected void ensureDelay() {
	// long current = System.currentTimeMillis();
	// long wait = previous + delay - current;
	// if (wait > 0) {
	// log.debug(String.format("Sleeping for %s ms", wait));
	// try {
	// Thread.sleep(wait);
	// } catch (InterruptedException ex) {
	//
	// }
	// previous = current + wait;
	// } else
	// previous = current;
	// }

	/**
	 * NCBI usage restrictions: http://www.ncbi.nlm.nih.gov/books/NBK25497/ No
	 * more than 3 requests per second Run the first 3 requests as quickly as
	 * possible then sleep for the remainder of a second before the next one.
	 */
	protected void ensureDelay() {
		if (++count > 3) {
			long wait = System.currentTimeMillis() - previous;
			if (wait < 1000) {
				log.debug("sleeping for ms: " + (1000 - wait));
				try {
					Thread.sleep(1000 - wait);
				} catch (InterruptedException ex) {

				}
			}
			count = 0;
			previous = System.currentTimeMillis();
		}
	}

	protected String generateUrl(String url, Collection<Object> params) {
		StringBuffer sb = new StringBuffer();
		sb.append(url).append("?");
		Iterator<Object> iter = params.iterator();
		while (iter.hasNext()) {
			String name = iter.next().toString();
			String value = iter.hasNext() ? iter.next().toString() : "";
			sb.append(name).append("=").append(value);
			if (iter.hasNext())
				sb.append("&");
		}
		return sb.toString();
	}

	protected void ensureParams(String url, Collection<Object> params) {
		if (params.size() % 2 != 0)
			params.add("");
		params.add("tool");
		params.add(getTool());
		params.add("email");
		params.add(getEmail());
		if (!EINFO_URL.equals(url)) {
			params.add("version");
			params.add(getEUtilsVersion());
		}
	}

	public InputStream getInputStream(final String url, Object... params) throws IOException {
		List list = new ArrayList(params.length);
		list.addAll(Arrays.asList(params));
		return getInputStream(url, list);
	}

	public InputStream getInputStream(final String url, final Collection<Object> params) throws IOException {
		ensureDelay();
		InputStream toReturn = null;
		List list = new ArrayList(Arrays.asList(params));
		HttpPost post = new HttpPost(url);
		ensureParams(url, params);
		MultipartEntity entity = addParts(new MultipartEntity(), params);
		// UrlEncodedFormEntity entity = new
		// UrlEncodedFormEntity(getNameValuePairs(params));
		post.setEntity(entity);
		log.debug("Fetching from: " + generateUrl(url, params));
		long start = System.currentTimeMillis();
		HttpResponse response = getHttpClient().execute(post);
		long end = System.currentTimeMillis();
		log.debug(String.format("InputStream fetched in %sms", end - start));
		if (DEBUGGING) {
			File file = File.createTempFile("eutils", ".xml");
			log.info("DEBUG MODE: Copying eUtils stream to: " + file);
			BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
			response.getEntity().writeTo(bos);
			bos.flush();
			bos.close();
			toReturn = new BufferedInputStream(new FileInputStream(file));
		} else
			toReturn = response.getEntity().getContent();

		return toReturn;
	}
}